﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

using Microsoft.Xna.Framework;

namespace FontManager
{
    /// <summary>
    /// customize tab page. 
    /// Needed to use it since decided to include character edit window into the main window.
    /// Took the old code used for opening editor in a new window and refactored it to fit tabPage.
    /// </summary>
    class CustomTabPage : TabPage
    {
        /// <summary>
        /// delegate used to invoke refresh preview method in MainForm window.
        /// </summary>
        public delegate void sendRefreshRequest();
        public sendRefreshRequest RefreshRequest;

        /// <summary>
        /// picture box which is docked in tabPage and is used for drawing on it.
        /// </summary>
        public CustomPictureBox TabPictureBox;

        /// <summary>
        /// graphics objects which draws behind the scene
        /// </summary>
        private Graphics _graphicsObject;
        /// <summary>
        /// bitmap on which _graphicsObject objects draws everything
        /// </summary>
        private Bitmap _offscreenBitmap;

        /// <summary>
        /// the location of the image using this window coordinates
        /// </summary>
        private PointF _imageLocation;
        /// <summary>
        /// image of loaded character
        /// </summary>
        private Image _image;

        /// <summary>
        /// used to call Invalidate() method nonStop
        /// </summary>
        public static Timer TabTimer;
        /// <summary>
        /// character loaded here
        /// </summary>
        public FontCharacter Character { get; set; }

        Pen _pen = new Pen(System.Drawing.Color.Black, 1);
        Font _font = new Font("Arial", 7);
        Brush _brush = new SolidBrush(System.Drawing.Color.Black);

        /// <summary>
        /// constructor initializing everything.
        /// </summary>
        /// <param _name="_name">character value of the character which will be loaded here</param>
        public CustomTabPage(string name)
            : base(name)
        {
            _offscreenBitmap = new Bitmap(this.Width, this.Height);
            _graphicsObject = Graphics.FromImage(_offscreenBitmap);

            LINE_X = new FontLine(3, Height / 2, Width - 6, 1);
            LINE_X.Name = "X";
            LINE_Y = new FontLine(Width / 2, 3, 1, Height - 6);
            LINE_Y.Name = "Y";

            TabPictureBox = new CustomPictureBox();

            Initialize(name);

            TabPictureBox.Resize += new EventHandler(TAB_PICTURE_BOX_Resize);
            TabPictureBox.SizeChanged += new EventHandler(TAB_PICTURE_BOX_Resize);
            TabPictureBox.MouseWheel += new MouseEventHandler(TAB_PICTURE_BOX_MouseWheel);
            TabPictureBox.MouseDown += new MouseEventHandler(TAB_PICTURE_BOX_MouseDown);
            TabPictureBox.PreviewKeyDown += new PreviewKeyDownEventHandler(PictureBox_PreviewKeyDown);

            this.Controls.Add(TabPictureBox);
            this.Resize += new EventHandler(TAB_PICTURE_BOX_Resize);
            this.SizeChanged += new EventHandler(TAB_PICTURE_BOX_Resize);
            TabPictureBox.Dock = System.Windows.Forms.DockStyle.Fill;

            TabTimer = new Timer();
            TabTimer.Enabled = true;
            TabTimer.Interval = 1000 / 60;
            TabTimer.Tick += new EventHandler((o, s) => { Invalidate(); });
        }
        /// <summary>
        /// extracted from constructor and is used when want to reinitialize which font character is represented in the view.
        /// method is invoked when selected cell in data grid view changes
        /// </summary>
        /// <param _name="_name">character value of the character in font character list</param>
        public void Initialize(string name)
        {
            this.Text = name;
            this.Name = name;
            Character = MainForm.NEW_FONT.GetCharacter(name);
            _image = Image.FromFile(Character.Image);

            _imageLocation = new PointF((LINE_Y.X - Character.AnchorPoint.X), (LINE_X.Y - Character.AnchorPoint.Y));
            LINE_WIDTH = new FontLine((int)(_imageLocation.X + Character.Width), 3, (int)(_imageLocation.X + Character.Width), Height - 6);
            LINE_WIDTH.Name = "WIDTH";
        }

        /// <summary>
        /// reinitialize _offscreenBitmap, graphics, lines X,Y and Width with _imageLocation.
        /// All values which changes due to control size change.
        /// </summary>
        /// <param _name="sender"></param>
        /// <param _name="e"></param>
        void TAB_PICTURE_BOX_Resize(object sender, EventArgs e)
        {
            _offscreenBitmap = new Bitmap(this.Width, this.Height);
            _graphicsObject = Graphics.FromImage(_offscreenBitmap);

            LINE_X.Initialize(3, Height / 2, Width - 6, 1);
            LINE_Y.Initialize(Width / 2, 3, 1, Height - 6);
            _imageLocation = new PointF((LINE_Y.X - Character.AnchorPoint.X), (LINE_X.Y - Character.AnchorPoint.Y));
            LINE_WIDTH.Initialize((int)(_imageLocation.X + Character.Width), 3, (int)(_imageLocation.X + Character.Width), Height - 6);
        }

        /// <summary>
        /// draw method which deals with temp graphics creations;
        /// behind the scene image clearing;
        /// image drawing;
        /// lines drawing;
        /// drawing infront;
        /// </summary>
        /// <param _name="e"></param>
        protected override void  OnPaint(PaintEventArgs e)
        {
            //===============================================================================//
            //Draws character and deals with screen refresh & cleaning
            //===============================================================================//
            Graphics gx = TabPictureBox.CreateGraphics();
            _graphicsObject.Clear(MainForm.NEW_FONT.Background);

            _graphicsObject.DrawImage(_image, _imageLocation);
       
            drawingLines();
            gx.DrawImage(_offscreenBitmap, 0, 0);
        }

        /// <summary>
        /// line which is specific from window to window due to it being dependent on its size
        /// </summary>
        private static FontLine LINE_Y;
        /// <summary>
        /// line which is specific from window to window due to it being dependent on its size
        /// </summary>
        private static FontLine LINE_WIDTH;
        /// <summary>
        /// line which is specific from window to window due to it being dependent on its size
        /// </summary>
        private static FontLine LINE_X;
        /// <summary>
        /// checks Font properties to determine which lines should be drawed
        /// </summary>
        private void drawingLines()
        {
            if (MainForm.NEW_FONT.LineBase)
            {
                drawLine(LINE_X);
                drawLine(LINE_Y);
            }
            if (MainForm.NEW_FONT.LineWidth)
            {
                drawLine(LINE_WIDTH);
            }

            if (MainForm.NEW_FONT.LineLeading)
            {
                drawLine(XNAHelper.LINE_LEADING);
            }
            if (MainForm.NEW_FONT.LineXHeight)
            {
                drawLine(XNAHelper.LINE_X_HEIGHT);
            }
            if (MainForm.NEW_FONT.LineCapHeight)
            {
                drawLine(XNAHelper.LINE_CAP_HEIGHT);
            }
            if (MainForm.NEW_FONT.LineAscenderHeight)
            {
                drawLine(XNAHelper.LINE_ASCENDER_HEIGHT);
            }
            if (MainForm.NEW_FONT.LineDescenderHeight)
            {
                drawLine(XNAHelper.LINE_DESCENDER_HEIGHT);
            }
        }
        /// <summary>
        /// Draws line on the screen taking lines.LineColor as draw LineColor;
        /// leading, XHeight and capheight are done draw with the text on the right size to be able to read it.
        /// </summary>
        /// <param _name="line"></param>
        private void drawLine(FontLine line)
        {
            float xnaAlpha = XNAHelper.Alpha;
            float alpha = 255 * xnaAlpha;
            if (alpha > 255)
                alpha = 255;
            if (alpha < 1)
                alpha = 1;
            _pen.Color = System.Drawing.Color.FromArgb((int)alpha, line.LineColor.R, line.LineColor.G, line.LineColor.B);
            if(line.Name == "X" || line.Name == "Y")
            {
                _pen.Color = System.Drawing.Color.Red;
                _pen.Color = System.Drawing.Color.FromArgb((int)alpha, _pen.Color);
            }
            _brush = new SolidBrush(_pen.Color);
            /*
             * Fallowing code handles the problem with text beeing on the same side;
             * with this around 50-50 text are on both sides;
             */
            int positionX = (int)line.X;
            int positionY = (int)(line.Y - 10);
            if (line.Name == "_leading" || line.Name == "_xheight" || line.Name == "_capHeight")
            {
                positionX = (int)Width - (int)_graphicsObject.MeasureString(line.Name, _font).Width;
            }
            /*
             * checking just for baseY line;
             * since I want text to be draw above line I am using y-10, 
             * BUT that makes baseY text to disapear from the screen;
             */
            if (line.Name == "Y" || line.Name == "WIDTH")
            {
                positionY += 10;
                _graphicsObject.DrawLine(_pen, line.X, line.Y, line.X, line.Height);
            }
            else
            {
                positionY = (int)(line.Y - 10);
                _graphicsObject.DrawLine(_pen, line.X, line.Y, Width, line.Y);
            }
                _graphicsObject.DrawString(line.Name, _font, _brush, positionX, positionY);
        }

        /// <summary>
        /// the scale of drawed character (1f = 100%)
        /// </summary>
        private float _zoom = 1f;
        /// <summary>
        /// returns _zoom value;
        /// Sets value to _zoom and applies matrix transform to the graphics.
        /// </summary>
        public float Zoom
        {
            get
            {
                return _zoom;
            }
            set
            {
                _zoom = value;
                _graphicsObject.ResetTransform();
                _graphicsObject.TranslateTransform((Width + _image.Width) / 2, (Height - _image.Height) / 2);
                _graphicsObject.ScaleTransform(_zoom, _zoom);
                _graphicsObject.TranslateTransform(-(Width + _image.Width) / 2, -(Height - _image.Height) / 2);
            }
        }
        /// <summary>
        /// contains the mouse coordinates
        /// </summary>
        Microsoft.Xna.Framework.Rectangle _mouseRectangle;
        /// <summary>
        /// deals with the coordinates change when image scaled
        /// </summary>
        Matrix _inverse;
        void TAB_PICTURE_BOX_MouseWheel(object sender, MouseEventArgs e)
        {
            try
            {
                if (e.Delta > 0)
                {
                    Zoom+= .1f;
                }
                if (e.Delta < 0)
                {
                    Zoom -= .1f;
                }
                //XNA CODE FOR GETTING INVERTED MOUSE COORDINATE
                //===============================================================================================================//
                GetInversePosition(e);
                //===============================================================================================================//
                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// creates translation for the image applying the same matrixes which were used in zooming/scaling process;
        /// After matrix is created it is used to _inverse mouse position and update mouse rectangle values.
        /// </summary>
        /// <param _name="e"></param>
        private void GetInversePosition(MouseEventArgs e)
        {
            Matrix matrix = Matrix.CreateTranslation(new Vector3(-(Width + _image.Width) / 2, -(Height - _image.Height) / 2, 0)) *
                      Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
                      Matrix.CreateTranslation(new Vector3((Width + _image.Width) / 2, (Height - _image.Height) / 2, 0));

            _inverse = Matrix.Invert(matrix);
            Vector2 mousePosition = Vector2.Transform(new Vector2(e.X, e.Y), _inverse);
            _mouseRectangle = new Microsoft.Xna.Framework.Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
        }

        /// <summary>
        /// Char_rect was used to detect when mouse if inside the character bounding box.
        /// </summary>
        Microsoft.Xna.Framework.Rectangle _characterRectangle;
        /// <summary>
        /// when mouseButtons.Left pressed updates inverset _mouseRectangle values and creates dummy MouseUp,MouseMove event listeners.
        /// </summary>
        /// <param _name="sender"></param>
        /// <param _name="e"></param>
        private void TAB_PICTURE_BOX_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                GetInversePosition(e);
                _characterRectangle = new Microsoft.Xna.Framework.Rectangle((int)_imageLocation.X, (int)_imageLocation.Y, _image.Width, _image.Height);

                if (LINE_WIDTH.GetSourceRectangle.Intersects(_mouseRectangle))
                    _moveWidthLine = true;
                else
                    _moveWidthLine = false;

                if (_moveWidthLine)
                {
                    TabPictureBox.MouseUp += new MouseEventHandler(PictureBox_MouseUp);
                    TabPictureBox.MouseMove += new MouseEventHandler(PictureBox_MouseMove);
                }

                if (_mouseRectangle.X > LINE_Y.X && _mouseRectangle.X < LINE_WIDTH.X)
                {
                    TabPictureBox.MouseUp += new MouseEventHandler(PictureBox_MouseMoveAnchoring);
                    TabPictureBox.MouseMove += new MouseEventHandler(PictureBox_MouseUpAnchoring);
                }
            }
        }

        bool _moveWidthLine = false;
        /// <summary>
        /// temporary event which moves width line by draging it
        /// </summary>
        /// <param _name="sender"></param>
        /// <param _name="e"></param>
        private void PictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            GetInversePosition(e);
            LINE_WIDTH.X = _mouseRectangle.X;
            Character.Width = _mouseRectangle.X - _imageLocation.X; //update char width property;

        }
        /// <summary>
        /// removes temp events
        /// </summary>
        /// <param _name="sender"></param>
        /// <param _name="e"></param>
        private void PictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            TabPictureBox.MouseMove -= new MouseEventHandler(PictureBox_MouseMove);
            TabPictureBox.MouseUp -= new MouseEventHandler(PictureBox_MouseUp);
            RefreshRequest();
        }

        /// <summary>
        /// Temporary event which is created on mouseDown.
        /// Performs when character is dragged to change its anchor point
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PictureBox_MouseMoveAnchoring(object sender, MouseEventArgs e)
        {
            GetInversePosition(e);

            //The code used for relocationg character anchor point
            //Character.AnchorPointX = LINE_Y.X - _mouseRectangle.X;
            //Character.AnchorPointY = LINE_X.Y - _mouseRectangle.Y;
            //_imageLocation.X = LINE_Y.X - Character.AnchorPointX;
            //_imageLocation.Y = LINE_X.Y - Character.AnchorPointX;
        }

        /// <summary>
        /// removes temporary events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PictureBox_MouseUpAnchoring(object sender, MouseEventArgs e)
        {
            TabPictureBox.MouseMove -= new MouseEventHandler(PictureBox_MouseMoveAnchoring);
            TabPictureBox.MouseUp -= new MouseEventHandler(PictureBox_MouseUpAnchoring);
        }


        /// <summary>
        /// reinitializes image location and width line values.
        /// </summary>
        public void ReInitialize()
        {
            _imageLocation = new PointF((LINE_Y.X - Character.AnchorPoint.X), (LINE_X.Y - Character.AnchorPoint.Y));
            LINE_WIDTH.Initialize(_imageLocation.X + Character.Width, 3,_imageLocation.X + Character.Width, Height - 6);
        }

        /// <summary>
        /// since you can't drag the character around to change its anchor point
        /// this method allows you to move character (change its anchor) by Keys UP,DOWN,LEFT,RIGHT
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PictureBox_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.KeyData == Keys.Left)
            {
                Character.AnchorPointX--;
            }
            if (e.KeyData == Keys.Right)
            {
                Character.AnchorPointX++;
            }
            if (e.KeyData == Keys.Down)
            {
                Character.AnchorPointY--;
            }
            if (e.KeyData == Keys.Up)
            {
                Character.AnchorPointY++;
            }
            ReInitialize();
            RefreshRequest();
        }
    }
}
